/*!
gfs search，grep，whereis的 REST API
- 借助Regex对文件名进行正则匹配搜索
- 通过tantivy实现全文检索

*/
use crate::*;
use anyhow::anyhow;
use regex::Regex;
use std::{
    ops::Range,
    vec,
};
use axum::{  
    extract::Path,
    response::{IntoResponse, Json, Response},
};

/// 单次从数据库读取的数据条数
const GREP_SIZE: u32 = 100;

/**
对文件列表分批次从stat读取，并进行grep模式匹配，返回匹配成功的数据
 - 返回的数据进行了转换，从Stat变为SearchDoc，目的是fname保留完整的文件路径描述，content对匹配命中信息做标注处理。
 - 返回结果的fname，可以通过get_element_by_search_fname(fname:&str)->(i32, i32, String)得到定位3元素。
*/
async fn grep_filename_tasks(shared: &SharedHandle, qry: &str) -> Result< Vec< SearchDoc >> {
    let regex = Regex::new(qry).map_err(|_| anyhow!("error to regex"))?;
    let mut tasks: Vec<SearchDoc > = vec![];
    debug!(target:"grep", "Regex pattern = {}", qry);

    let mut counts = 0;
    loop {
        let from: i32 = (GREP_SIZE * counts) as i32;
        let sub_tasks = shared.get_next_from_id(from, GREP_SIZE).await?;
        let rc = sub_tasks.len() as u32;
        let mut matches: Vec<_> = sub_tasks
            .into_iter()
            .map(|x| {
                let ret = regex.find(x.name.as_str());
                let mut y= SearchDoc{docid:"".to_string(), fname:"".to_string(), content:"".to_string(),score:0.0 };                
                if let Some(mat) = ret {                 
                    let Range { start, end } = mat.range();
                    y.content = format!(
                        "{}<mark>{}</mark>{}",
                        &x.name.as_str()[..start],
                        &qry,
                        &x.name.as_str()[end..]
                    );
                    y.fname = format!("{}/{:02x}/{}",x.fhash, x.id, x.name.as_str());
                }
               y
            })
            .filter(|x| x.content.len() > 0)            
            .collect();
        tasks.append(&mut matches);
        if rc == GREP_SIZE {
            counts += 1;
        } else {
            break;
        }
    }
    Ok(tasks)
}


/**
在当前服务器进行文件名grep
  - syntax: /grep/file/:pattern
  - grep只是针对文件名，借助stat表数据进行的查找。 
 */
pub async fn gfs_grep_file(
    Path(qry): Path<String>,
    State(shared): State<SharedHandle>,
) -> AResult<Response, MyError> {
    let grep_res = grep_filename_tasks(&shared, qry.as_str()).await?;
    Ok(Json(grep_res).into_response())
}

/**
在当前服务器进行的搜索查询
 - syntax：/search?q=qry&qtype=term&page=1   
 */
 
pub async fn gfs_search(
    Query(para): Query<SearchParams>,
    State(shared): State<SharedHandle>,
) -> AResult<Response, MyError> {
    debug!("{:#?}", para);
    let mut rets :Vec<SearchDoc> = vec![];
    if para.q.len() > 0 {
        rets = node_search(&shared.index, para.q.as_str(), para.t.as_str(), para.page).await?;
    }    
    Ok(Json(rets).into_response())
}

/**
获取一个文件名在当前服务器的信息Stat
 - syntax: whereis/:name    
 - 不同于grep，这里的文件名是全匹配，而grep是文件名局部匹配
 - 可能存在多个相同名字的多条数据
 - whereis的基础，是基于stat表数据实现的，但name没有创建index，希望fhash的索引已经最大化过滤了数据，降低name匹配的效率损失
 */
pub async fn gfs_whereis(
    Path(fname): Path<String>,
    State(shared): State<SharedHandle>,
) -> AResult<Response, MyError> {
    let limit = 100;
    let fhash = format!("{:02x}", string_hash256(fname.as_str()));
    let rets = shared.gfs.get_by_name(fhash.as_str(), fname.as_str(), 0, limit).await?;
    Ok(Json(rets).into_response())
}